﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SyncLib;
using System.ServiceModel;
using Microsoft.Synchronization;
using log4net;
using System.IO;
using SyncLib.Interface;
using Microsoft.Synchronization.Data.SqlServer;
using Microsoft.Synchronization.Data;
using SyncLib.Data;

namespace SyncWCFServiceLibrary
{
    public class RelationalSyncContract : SyncConfigManager, IRelationalSyncContract
    {
        static ILog Log = LogManager.GetLogger(typeof(Log4NetImplement));
        protected IKeeperSync KeeperSync { get; set; }
        protected Dictionary<string, string> batchIdToFileMapper;
        protected DirectoryInfo sessionBatchingDirectory = null;
        protected Microsoft.Synchronization.SyncOrchestratorState OrchestratorState { get; set; }
        // protected Guid ClientId { get; set; }

        #region IRelationalSyncContract Members

        public void BeginSession(SyncProviderPosition position)
        {
            try
            {
                Log.Info("*****************************************************************");
                Log.Info("******************** New Sync Session ***************************");
                Log.Info("*****************************************************************");
                Log.InfoFormat("Client {0} - Scope {1} BeginSession ", ClientId, KeeperSync.Context.CurrentScope.ScopeName);

                batchIdToFileMapper.Clear();
                KeeperSync.Provider.BeginSession(position, null);
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("BeginSession Exception", ex));
            }
        }

        public void EndSession()
        {
            try
            {
                CleanBatchFolder();
                KeeperSync.Provider.EndSession(null);
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("EndSession Exception", ex));
            }
        }

        public KChangesParameter GetChangeBatch(uint batchSize, Microsoft.Synchronization.SyncKnowledge destinationKnowledge)
        {
            try
            {
                KChangesParameter changeWap = new KChangesParameter();
                changeWap.ChangeBatch = KeeperSync.Provider.GetChangeBatch(batchSize, destinationKnowledge, out changeWap.DataRetriever);
                DbSyncContext context = changeWap.DataRetriever as DbSyncContext;
                //Check to see if data is batched
                if (context != null && context.IsDataBatched)
                {


                    //Dont send the file location info. Just send the file name
                    string fileName = new FileInfo(context.BatchFileName).Name;
                    this.batchIdToFileMapper[fileName] = context.BatchFileName;
                    context.BatchFileName = fileName;
                }
                return changeWap;
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("GetChangeBatch Exception", ex));
            }
        }

        public KChangesParameter GetFullEnumerationChangeBatch(uint batchSize, Microsoft.Synchronization.SyncId lowerEnumerationBound, Microsoft.Synchronization.SyncKnowledge knowledgeForDataRetrieval)
        {
            try
            {

                KChangesParameter changeWap = new KChangesParameter();
                changeWap.FullChangeBatch = KeeperSync.Provider.GetFullEnumerationChangeBatch(batchSize, lowerEnumerationBound, knowledgeForDataRetrieval, out changeWap.DataRetriever);
                DbSyncContext context = changeWap.DataRetriever as DbSyncContext;
                //Check to see if data is batched
                if (context != null && context.IsDataBatched)
                {


                    //Dont send the file location info. Just send the file name
                    string fileName = new FileInfo(context.BatchFileName).Name;
                    this.batchIdToFileMapper[fileName] = context.BatchFileName;
                    context.BatchFileName = fileName;
                }
                return changeWap;
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("GetFullEnumerationChangeBatch Exception", ex));
            }
        }

        public KSyncBatchParameter GetSyncBatchParameters()
        {
            try
            {
                KSyncBatchParameter syncBatchParameterWap = new KSyncBatchParameter();
                KeeperSync.Provider.GetSyncBatchParameters(out syncBatchParameterWap.BatchSize, out syncBatchParameterWap.DestinationKnowledge);
                return syncBatchParameterWap;
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("GetFullEnumerationChangeBatch Exception", ex));
            }
        }

        public Microsoft.Synchronization.SyncIdFormatGroup GetIdFormats()
        {
            try
            {
                return KeeperSync.Provider.IdFormats;
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("GetFullEnumerationChangeBatch Exception", ex));
            }
        }

        public SyncSessionStatistics ProcessChangeBatch(Microsoft.Synchronization.ConflictResolutionPolicy resolutionPolicy, Microsoft.Synchronization.ChangeBatch sourceChanges, object changeDataRetriever, Microsoft.Synchronization.SyncSessionStatistics sessionStatistics)
        {
            SyncSessionStatistics localStatistics = new SyncSessionStatistics();
            try
            {
                DbSyncContext dataRetriever = changeDataRetriever as DbSyncContext;

                if (dataRetriever != null && dataRetriever.IsDataBatched)
                {
                    string remotePeerId = dataRetriever.MadeWithKnowledge.ReplicaId.ToString();
                    //Data is batched. The client should have uploaded this file to us prior to calling ApplyChanges.
                    //So look for it.
                    //The Id would be the DbSyncContext.BatchFileName which is just the batch file name without the complete path
                    string localBatchFileName = null;
                    if (!this.batchIdToFileMapper.TryGetValue(dataRetriever.BatchFileName, out localBatchFileName))
                    {
                        //Service has not received this file. Throw exception
                        throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("No batch file uploaded for id " + dataRetriever.BatchFileName, null));
                    }
                    dataRetriever.BatchFileName = localBatchFileName;
                }

                KeeperSync.Provider.ProcessChangeBatch(resolutionPolicy, sourceChanges, changeDataRetriever, new SyncCallbacks(), localStatistics);
                return localStatistics;
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("ProcessChangeBatch Exception", ex));
            }
        }

        public SyncSessionStatistics ProcessFullEnumerationChangeBatch(Microsoft.Synchronization.ConflictResolutionPolicy resolutionPolicy, Microsoft.Synchronization.FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, Microsoft.Synchronization.SyncSessionStatistics sessionStatistics)
        {
            SyncSessionStatistics localStatis = new SyncSessionStatistics();
            try
            {
                KeeperSync.Provider.ProcessFullEnumerationChangeBatch(resolutionPolicy, sourceChanges, changeDataRetriever, new SyncCallbacks(), localStatis);
                return localStatis;
            }
            catch (Exception ex)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("ProcessChangeBatch Exception", ex));
            }
        }

        public byte[] DownloadBatchFile(string fileName)
        {

            try
            {

                Stream localFileStream = null;

                string localBatchFileName = null;

                if (!this.batchIdToFileMapper.TryGetValue(fileName, out localBatchFileName))
                {
                    throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("Unable to retrieve batch file for id." + fileName, null));
                }

                using (localFileStream = new FileStream(localBatchFileName, FileMode.Open, FileAccess.Read))
                {
                    byte[] contents = new byte[localFileStream.Length];
                    localFileStream.Read(contents, 0, contents.Length);
                    return contents;
                }
            }
            catch (Exception e)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("Unable to read batch file for id " + fileName, e));
            }
        }

        public void UploadBatchFile(string batchFileId, byte[] batchContents, string remotePeerId)
        {
            try
            {
                if (HasUploadedBatchFile(batchFileId, remotePeerId))
                {
                    //Service has already received this file. So dont save it again.
                    return;
                }

                //Service hasnt seen the file yet so save it.
                String localFileLocation = Path.Combine(sessionBatchingDirectory.FullName, batchFileId);
                FileStream fs = new FileStream(localFileLocation, FileMode.Create, FileAccess.Write);
                using (fs)
                {
                    fs.Write(batchContents, 0, batchContents.Length);
                }
                //Save this Id to file location mapping in the mapper object
                this.batchIdToFileMapper[batchFileId] = localFileLocation;
            }
            catch (Exception e)
            {
                throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("Unable to save batch file.", e));
            }
        }

        public bool HasUploadedBatchFile(string batchFileId, string remotePeerId)
        {
            this.CheckAndCreateBatchingDirectory(remotePeerId);

            //The batchFileId is the fileName without the path information in it.
            FileInfo fileInfo = new FileInfo(Path.Combine(this.sessionBatchingDirectory.FullName, batchFileId));
            if (fileInfo.Exists && !this.batchIdToFileMapper.ContainsKey(batchFileId))
            {
                //If file exists but is not in the memory id to location mapper then add it to the mapping
                this.batchIdToFileMapper.Add(batchFileId, fileInfo.FullName);
            }
            //Check to see if the proxy has already uploaded this file to the service
            return fileInfo.Exists;
        }

        protected void CheckAndCreateBatchingDirectory(string remotePeerId)
        {
            //Check to see if we have temp directory for this session.
            if (sessionBatchingDirectory == null)
            {

                //Generate a unique Id for the directory
                //We use the peer id of the store enumerating the changes so that the local temp directory is same for a given source
                //across sync sessions. This enables us to restart a failed sync by not downloading already received files.
                string sessionDir = Path.Combine(this.KeeperSync.Provider.BatchingDirectory, "WebSync_" + remotePeerId);
                sessionBatchingDirectory = new DirectoryInfo(sessionDir);
                //Create the directory if it doesnt exist.
                if (!sessionBatchingDirectory.Exists)
                {
                    sessionBatchingDirectory.Create();
                }
            }
        }

        public void ChangeState(Microsoft.Synchronization.SyncOrchestratorState state)
        {
            OrchestratorState = state;
        }

        #endregion


        private void CleanBatchFolder()
        {
            try
            {
                if (CommonVariable.CleanBatch)
                {
                    if (sessionBatchingDirectory != null && sessionBatchingDirectory.Exists)
                    {
                        sessionBatchingDirectory.Delete(true);
                    }
                    if (Directory.Exists(CommonVariable.SyncFolderBatch + "\\" + ClientId.ToString()))
                        Directory.Delete(CommonVariable.SyncFolderBatch + "\\" + ClientId.ToString(), true);
                }
            }
            catch (Exception ex)
            {
                Log.Fatal("Can't delete batch folder",ex);
            }
        }


    }
}
